<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
	<title>React Demo</title>
  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
	<style>
    body {
      margin: 0;
    }
		#root {
			padding: 10px;
		}
    .num {
      padding: 5px;
      border: 1px solid #ddd;
    }
    .num h2 {
      margin: 0;
      text-align: left;
    }
    .num__value {
      font-size: 36px;
      font-family: serif;
      font-weight: bold;
      color: #666;
    }
    .counter {
      width: 250px;
      padding: 0 25px;
      border: 2px dotted #333;
      border-radius: 10px;
      text-align: center;
    }
    .counter h2 {
      margin: 5px 0;
      text-align: left;
    }
    .counter__button-increase {
      border-radius: 5px;
      cursor: pointer;
      outline: none;
    }
    .counter__notice {
      margin: 0;
      font-size: 12px;
    }
	</style>
</head>
<body>
   <div id="root"></div>

   <script type="text/babel">

    /**
     * React 组件的生命周期
     * 包括三种状态：Mounted（挂载）、Update（更新）、Unmounted（卸载），每一种状态 React 都封装了对应的 hook 函数（生命周期函数）
     *
     * Mounting：组件被 render 解析生成对应的 DOM 节点， 并被插入到浏览器的 DOM 结构的一个过程
     * getDefaultProps() -> getInitialState() -> componentWillMount() -> render() -> componentDidMount()
     *
     * Updating：一个 Mounted 的组件被重新 render 的过程
     * React 组件更新机制：setState引起的state更新或父组件重新render引起的props更新，更新后的state和props相对之前无论是否有变化，都将引起子组件的重新render
     * componentWillReceiveProps() -> shouldComponentUpdate() -> componentWillUpdate() -> render() -> componentDidUpdate()
     *
     * Unmounting：一个 Mounted 的组件对应的 DOM 节点被从 DOM 结构中移除的一个过程
     * componentWillUnmount()
     */
		class Counter extends React.Component {

      constructor(props) {
        super(props);

        // 初始化组件的私有数据
        this.state = {
          isClick: false
        }
      }

      /**
       * 父组件发生 render 时，子组件就会调用 componentWillReceiveProps() 方法（不管父组件 props 有没有更新，也不管父子组件之间有没有数据交换）
       * 组件是由自身 state 更新而更新的，则不会执行
       */
			componentWillReceiveProps(newProps) {
				console.log('componentWillReceiveProps: child');
			}

      /**
			 * 组件挂载之后，每次调用 setState() 方法修改组件状态后都会调用 shouldComponentUpdate() 方法判断是否需要重新渲染组件
			 * 默认返回 true，即需要重新 render；返回 false 时，组件不再向下执行生命周期函数
			 * 在比较复杂的应用里，有一些数据的改变并不影响界面展示，可以在这里做判断，优化渲染效率
			 */
			shouldComponentUpdate(newProps, newState) {
				console.log('shouldComponentUpdate: child');
				return true;
			}

			componentWillUpdate() {
				console.log('componentWillUpdate: child');
			}

      componentDidUpdate() {
				console.log('componentDidUpdate: child');
			}

      handleToClick = () => {
        this.setState({
          isClick: true
        })
      }

			render() {
        const { count } = this.props;
				return (
          <div className="num">
            <h2>子组件：</h2>
            更新组件自身状态值：<button onClick={this.handleToClick}>点击</button>
					  <p className="num__value">Counter：{count}</p>
          </div>
				)
			}
		}
		
    class CounterParent extends React.Component {
      
			/* 
			 * 构造函数，初始化组件状态值，对应着组件生命周期函数 getInitialState()
			 * 在创建组件的时候调用一次
			 */

      // constructor() 用来做一些组件的初始化工作，如定义this.state的初始内容。
			constructor(props) {

        /**
         * super(props) 用来调用基类的构造方法 constructor(), 也将父组件的 props 注入给子组件，供子组件读取(组件中 props 只读不可变，state 可变)。
         */
				super(props);
				this.state = {
					count: 0,
          test: 1
				}
			}

			/**
			 * 在组件挂载之前调用一次
			 * 如果在这个函数里面调用 setState() 方法，本次的render函数可以看到更新后的state，并且只渲染一次
       * 一般在这个阶段发送 ajax 请求
			 */
			componentWillMount() {
				console.log('componentWillMount: parent');
			}

			shouldComponentUpdate(newProps, newState) {
				console.log('shouldComponentUpdate: parent');
        const { count } = newState;
				if(count <= 10) {
					console.log('Component should update!');
					return true;
				} else {
					console.log('Component should not update!');
					if (ReactDOM.unmountComponentAtNode(root)){
						console.log('Component is removed!');
					}else {
						console.log('Component is not removed!');
					}
					return false;
				}
			}

			componentWillUpdate() {
				console.log('componentWillUpdate: parent');
			}

      // 除了首次 render 之后调用 componentDidMount() 方法，其它 render 结束之后都是调用 componentDidUpdate()
			componentDidUpdate() {
				console.log('componentDidUpdate: parent');
			}

      /**
			 * 组件挂载之后调用一次
			 * 此时，子组件也都挂载好了，可以在这里使用 refs，可以操作页面上的 DOM 元素
			 */
			componentDidMount() {
				console.log('componentDidMount: parent');
			}

			/**
			 * 组件被卸载的时候调用
			 * 一般在 componentDidMount() 方法里面注册的事件需要在这里删除
			 */
			componentWillUnmount() {
				console.log('componentWillUnmount: parent');
			}

			// 参数 e 是指事件的属性对象，实际指 React 封装的一个特定对象 SyntheticEvent（合成事件）
			increase(e) {
        let { count } = this.state;
				e.shiftKey? count += 2 : count += 1;
				this.setState({
					count
				});
			}

      handleChange = () => {
        console.log('test');
        this.setState({
          test: 5
        })
      }

			/**
			 * render是一个React组件所必不可少的核心函数（其它生命周期函数都不是必须的）
			 * 记住，不要在render里面修改state
			 */
			render() {
        const { color } = this.props;
				return (
					<div className="counter">
            <h2>父组件：</h2>
						<Counter count={this.state.count} />
						<button
              className="counter__button-increase"
              style={{
                color
              }}
              onClick={this.increase.bind(this)}
            >+</button>
            <button onClick={this.handleChange}>test</button>
						<p className="counter__notice">(按shift键点击，每次增加2)</p>
					</div>
				)
			}
		}

		/**
		 * 类组件的属性只能定义在类之外
		 * 由于是用 ES6 class 语法创建组件，其内部只允许定义方法，而不能定义属性，所以 defaultProps 要写在组件外部
		 */
		CounterParent.defaultProps = {
			color: "#ff0000"
		}

		class App extends React.Component {
			render() {
				return (
					<React.Fragment>
						<CounterParent />
					</React.Fragment>
				);
			}
		}

    ReactDOM.render(
      <App />, 
      document.querySelector('#root')
    );
  </script>
</body>
</html>